import os
import uuid
import oss2
from datetime import datetime
from flask import current_app
from werkzeug.utils import secure_filename

class AliyunOSS:
    """
    阿里云OSS操作类
    提供上传文件、获取文件URL、删除文件等功能
    """

    _instance = None

    def __new__(cls, *args, **kwargs):
        """单例模式实现"""
        if cls._instance is None:
            cls._instance = super(AliyunOSS, cls).__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self, access_key_id=None, access_key_secret=None, endpoint=None, bucket_name=None):
        """
        初始化OSS客户端

        Args:
            access_key_id: 阿里云AccessKey ID
            access_key_secret: 阿里云AccessKey Secret
            endpoint: OSS区域节点地址
            bucket_name: OSS存储空间名称
        """
        # 如果已经初始化过，则直接返回
        if getattr(self, '_initialized', False):
            return

        # 优先使用传入的参数，如果没有则使用配置文件中的参数
        self.access_key_id = access_key_id or current_app.config.get('ALIYUN_OSS_ACCESS_KEY_ID')
        self.access_key_secret = access_key_secret or current_app.config.get('ALIYUN_OSS_ACCESS_KEY_SECRET')
        self.endpoint = endpoint or current_app.config.get('ALIYUN_OSS_ENDPOINT')
        self.bucket_name = bucket_name or current_app.config.get('ALIYUN_OSS_BUCKET_NAME')
        self.bucket_domain = current_app.config.get('ALIYUN_OSS_BUCKET_DOMAIN', f'https://{self.bucket_name}.{self.endpoint}')

        # 确保必要的配置参数存在
        if not all([self.access_key_id, self.access_key_secret, self.endpoint, self.bucket_name]):
            raise ValueError("阿里云OSS配置不完整，请检查配置文件或传入参数")

        # 创建Auth和Bucket实例
        self.auth = oss2.Auth(self.access_key_id, self.access_key_secret)
        self.bucket = oss2.Bucket(self.auth, self.endpoint, self.bucket_name)
        self._initialized = True

    def upload_file(self, file_obj, folder='uploads', filename=None, content_type=None):
        """
        上传文件对象到OSS

        Args:
            file_obj: 文件对象或文件路径
            folder: OSS中的文件夹路径
            filename: 自定义文件名，如果不提供则生成随机文件名
            content_type: 文件的MIME类型

        Returns:
            dict: 包含文件URL和OSS对象名称的字典
        """
        # 处理文件名
        if filename:
            # 安全处理文件名，避免安全问题
            filename = secure_filename(filename)
        else:
            # 如果是文件对象，尝试获取原始文件名
            if hasattr(file_obj, 'filename'):
                original_filename = secure_filename(file_obj.filename)
                _, ext = os.path.splitext(original_filename)
            else:
                # 如果是文件路径，获取扩展名
                if isinstance(file_obj, str) and os.path.isfile(file_obj):
                    _, ext = os.path.splitext(file_obj)
                else:
                    ext = ''

            # 生成随机文件名
            filename = f"{uuid.uuid4().hex}{ext}"

        # 构建OSS对象名称（OSS中的完整路径）
        date_folder = datetime.now().strftime('%Y%m%d')
        object_name = f"{folder}/{date_folder}/{filename}"

        # 上传文件
        try:
            # 如果是文件路径
            if isinstance(file_obj, str) and os.path.isfile(file_obj):
                # 上传本地文件
                result = self.bucket.put_object_from_file(object_name, file_obj)
            else:
                # 上传文件对象
                headers = {}
                if content_type:
                    headers['Content-Type'] = content_type

                # 如果是文件对象，获取文件内容
                if hasattr(file_obj, 'read'):
                    file_content = file_obj.read()
                else:
                    file_content = file_obj

                result = self.bucket.put_object(object_name, file_content, headers=headers)

            # 检查上传是否成功
            if result.status == 200:
                return {
                    'url': f"{self.bucket_domain}/{object_name}",
                    'object_name': object_name
                }
            else:
                raise Exception(f"文件上传失败，状态码：{result.status}")
        except Exception as e:
            current_app.logger.error(f"OSS上传失败: {str(e)}")
            raise

    def upload_local_file(self, file_path, folder='uploads', filename=None):
        """
        上传本地文件到OSS

        Args:
            file_path: 本地文件路径
            folder: OSS中的文件夹路径
            filename: 自定义文件名，如果不提供则使用原文件名

        Returns:
            dict: 包含文件URL和OSS对象名称的字典
        """
        if not os.path.isfile(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")

        # 如果没有提供文件名，使用原文件名
        if not filename:
            filename = os.path.basename(file_path)

        return self.upload_file(file_path, folder, filename)

    def get_file_url(self, object_name):
        """
        获取OSS文件的URL

        Args:
            object_name: OSS对象名称

        Returns:
            str: 文件的URL
        """
        return f"{self.bucket_domain}/{object_name}"

    def get_signed_url(self, object_name, expires=3600):
        """
        获取带签名的临时访问URL

        Args:
            object_name: OSS对象名称
            expires: URL有效期（秒），默认1小时

        Returns:
            str: 带签名的临时访问URL
        """
        # 生成签名URL
        url = self.bucket.sign_url('GET', object_name, expires)
        return url

    def delete_file(self, object_name):
        """
        删除OSS上的文件

        Args:
            object_name: OSS对象名称

        Returns:
            bool: 删除是否成功
        """
        try:
            result = self.bucket.delete_object(object_name)
            return result.status == 204  # 204表示删除成功
        except Exception as e:
            current_app.logger.error(f"OSS删除文件失败: {str(e)}")
            return False

    def check_file_exists(self, object_name):
        """
        检查OSS上的文件是否存在

        Args:
            object_name: OSS对象名称

        Returns:
            bool: 文件是否存在
        """
        try:
            return self.bucket.object_exists(object_name)
        except Exception as e:
            current_app.logger.error(f"OSS检查文件失败: {str(e)}")
            return False

    def list_files(self, prefix='', delimiter=''):
        """
        列出OSS上的文件

        Args:
            prefix: 前缀，用于筛选文件
            delimiter: 分隔符，通常用于模拟文件夹结构

        Returns:
            list: 文件列表
        """
        try:
            files = []
            for obj in oss2.ObjectIterator(self.bucket, prefix=prefix, delimiter=delimiter):
                if obj.is_prefix():  # 文件夹
                    files.append({
                        'name': obj.key,
                        'type': 'folder'
                    })
                else:  # 文件
                    files.append({
                        'name': obj.key,
                        'size': obj.size,
                        'last_modified': obj.last_modified,
                        'type': 'file',
                        'url': self.get_file_url(obj.key)
                    })
            return files
        except Exception as e:
            current_app.logger.error(f"OSS列出文件失败: {str(e)}")
            return []

class LazyAliyunOSS:
    """
    延迟初始化的AliyunOSS代理类
    确保在应用上下文中初始化AliyunOSS实例
    """
    _instance = None

    def __getattr__(self, name):
        if self._instance is None:
            from flask import current_app
            # 确保在应用上下文中初始化
            if not current_app:
                raise RuntimeError("Working outside of application context. "
                                  "Make sure to call this within a Flask request or application context.")
            self._instance = AliyunOSS()
        return getattr(self._instance, name)

# 创建延迟初始化的实例，方便全局使用
aliyun_oss = LazyAliyunOSS()


# 创建延迟初始化的代理类，避免在应用上下文外初始化
